package com.ucode.tool.util;
import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;

import cn.hutool.core.date.SystemClock;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;

/**
 * 分布式高效有序ID生成器
 * sequence:http://git.oschina.net/yu120/sequence
 * hutool:https://gitee.com/loolly/hutool/blob/v5-master/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java
 * 优化hutool自带的Snowflake,解决了时间回拨问题的优化方案如下：
 * 1. 如果发现当前时间少于上次生成id的时间(时间回拨)，着计算回拨的时间差
 * 2. 如果时间差(offset)小于等于5ms，着等待 offset * 2 的时间再生成
 * 3. 如果offset大于5，则直接抛出异常
 * 备注:如果建立统一全局的ID服务生成中心，可参考https://gitee.com/zmds/ecp-uid
 *
 * Twitter_Snowflake<br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -
 * 000000000000 <br>
 * 1位标识，由于long基本类型在Java中是带符号的，最高位是符号位，正数是0，负数是1，所以id一般是正数，最高位是0<br>
 * 41位时间截(毫秒级)，注意，41位时间截不是存储当前时间的时间截，而是存储时间截的差值（当前时间截 - 开始时间截)
 * 得到的值），这里的的开始时间截，一般是我们的id生成器开始使用的时间，由我们程序来指定的（如下下面程序IdWorker类的startTime属性）。41位的时间截，可以使用69年，年T
 * = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的数据机器位，可以部署在1024个节点，包括5位datacenterId和5位workerId<br>
 * 12位序列，毫秒内的计数，12位的计数顺序号支持每个节点每毫秒(同一机器，同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位，为一个Long型。<br>
 * SnowFlake的优点是，整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分)，并且效率较高，经测试，SnowFlake每秒能够产生26万ID左右。
 * @author liliang
 */
public class Snowflake implements Serializable{
	private static final long serialVersionUID = 1L;
	
	private static Log log = LogFactory.get(Snowflake.class);
	
    /**
     * 回拨超时错误
     */
    private final static String ERROR_CLOCK_BACK = "时间回拨，拒绝为超出{}毫秒生成ID";
    private final static String ERROR_ATTR_LIMIT = "{}属性的范围为0-{}";
    
    /** 开始时间截 (2017-12-25)，用于用当前时间戳减去这个时间戳，算出偏移量 */
	private final long twepoch = 1288834974657L;
	 /** 机器id所占的位数(表示只允许workId的范围为：0-1023) */
	private final long workerIdBits = 5L;
	/** 数据标识id所占的位数 */
	private final long datacenterIdBits = 5L;
	/** 支持的最大机器id，结果是31 (机器节点数0~31，一共32个,这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
	private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
	/** 支持的最大数据标识id，结果是31(数据中心节点数0~31，一共32个) */
	private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
	/** 序列在id中占的位数 (表示只允许sequenceId的范围为：0-4095)*/
	private final long sequenceBits = 12L;
	/** 机器ID向左移12位 */
	private final long workerIdShift = sequenceBits;
	/** 数据标识id向左移17位(12+5) */
	private final long datacenterIdShift = sequenceBits + workerIdBits;
	/** 时间截向左移22位(5+5+12) */
	private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
	/** 生成序列的掩码，(防止溢出:位与运算保证计算的结果范围始终是 0-4095，0b111111111111=0xfff=4095) */
	private final long sequenceMask = -1L ^ (-1L << sequenceBits);// 4095
	
	/** 工作机器ID(0~31) */
	private long workerId;
	/** 数据中心ID(0~31) */
	private long datacenterId;
	/** 毫秒内序列(0~4095) */
	private long sequence = 0L;
	/** 上次生成ID的时间截 */
	private long lastTimestamp = -1L;
	
	 /**时间回拨最长时间(ms)，超过这个时间就抛出异常*/
    private long timestampOffset = 5L;
	
	private boolean useSystemClock;
	
	/**
	 * 默认
	 */
	public Snowflake() {
	   this.datacenterId = getDatacenterId(maxDatacenterId);
	   this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
	   this.useSystemClock = true;
	}

	/**
	 * 构造
	 * @param workerId 机器ID (0~31)
	 * @param datacenterId 数据中心ID (0~31)
	 */
	public Snowflake(long workerId, long datacenterId) {
		this(workerId, datacenterId, true);
	}

	/**
	 * 构造
	 * 
	 * @param workerId 终端ID
	 * @param datacenterId 数据中心ID
	 * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
	 */
	public Snowflake(long workerId, long datacenterId, boolean isUseSystemClock) {
		if (workerId > maxWorkerId || workerId < 0) {
		    throw new IllegalArgumentException(String.format(ERROR_ATTR_LIMIT, "workerId", maxWorkerId));
		}
		if (datacenterId > maxDatacenterId || datacenterId < 0) {
		    throw new IllegalArgumentException(String.format(ERROR_ATTR_LIMIT, "datacenterId", maxDatacenterId));
		}
		this.workerId = workerId;
		this.datacenterId = datacenterId;
		this.useSystemClock = isUseSystemClock;
	}
	
	
	/**
	 * 获取 maxWorkerId
	 * @author liliang
	 * @date 2019年12月16日
	 * @param datacenterId
	 * @param maxWorkerId
	 * @return
	 */
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuilder mpid = new StringBuilder();
        mpid.append(datacenterId);
        //获取当前JVM进程的PID
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (StrUtil.isNotEmpty(name)) {
            /*
             * GET jvmPid
             */
            mpid.append(name.split("@")[0]);
        }
        /*
         * MAC + PID 的 hashcode 获取16个低位
         */
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * 数据标识id部分
     * @author liliang
     * @date 2019年12月16日
     * @param maxDatacenterId
     * @return
     */
    protected static long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                if (null != mac) {
                    id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                    id = id % (maxDatacenterId + 1);
                }
            }
        } catch (Exception e) {
            log.warn(e, "Snowflake getDatacenterId error!");
        }
        return id;
    }
	
	/**
	 * 根据Snowflake的ID，获取机器id
	 *
	 * @param id snowflake算法生成的id
	 * @return 所属机器的id
	 */
	public long getWorkerId(long id) {
		return id >> workerIdShift & ~(-1L << workerIdBits);
	}

	/**
	 * 根据Snowflake的ID，获取数据中心id
	 *
	 * @param id snowflake算法生成的id
	 * @return 所属数据中心
	 */
	public long getDataCenterId(long id) {
		return id >> datacenterIdShift & ~(-1L << datacenterIdBits);
	}

	/**
	 *根据Snowflake的ID，获取生成时间
	 *
	 * @param id snowflake算法生成的id
	 * @return 生成的时间
	 */
	public long getGenerateDateTime(long id) {
		return (id >> timestampLeftShift & ~(-1L << 41L)) + twepoch;
	}

	/**
	 * 获得下一个ID (该方法是线程安全的)
	 * 
	 * @return ID
	 */
	public synchronized long nextId() {
		long timestamp = genTime();
		// 闰秒：如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
		if (timestamp < lastTimestamp) {
		    
		    long offset = lastTimestamp - timestamp;
		    if (offset <= timestampOffset) {
		        try {
                    // 时间偏差大小小于5ms，则等待两倍时间
                    wait(offset << 1);
                    timestamp = genTime();
                    if (timestamp < lastTimestamp) {
                        // 还是小于，抛异常并上报
                        throw new RuntimeException(String.format(ERROR_CLOCK_BACK, lastTimestamp - timestamp));
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
		    }else{
		        throw new RuntimeException(String.format(ERROR_CLOCK_BACK, lastTimestamp - timestamp));
		    }
		}
		
		// 解决跨毫秒生成ID序列号始终为偶数的缺陷:如果是同一时间生成的，则进行毫秒内序列
		if (lastTimestamp == timestamp) {
		    // 通过位与运算保证计算的结果范围始终是 0-4095
			sequence = (sequence + 1) & sequenceMask;
			 // 毫秒内序列溢出
			if (sequence == 0) {
			    // 阻塞到下一个毫秒,获得新的时间戳
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
		    // 时间戳改变，毫秒内序列重置
			sequence = 0L;
		}
		// 上次生成ID的时间截
		lastTimestamp = timestamp;
		/*
         * 1.左移运算是为了将数值移动到对应的段(41、5、5，12那段因为本来就在最右，因此不用左移)
         * 2.然后对每个左移后的值(la、lb、lc、sequence)做位或运算，是为了把各个短的数据合并起来，合并成一个二进制数
         * 3.最后转换成10进制，就是最终生成的id(64位的ID)
         */
		return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
	}
	
	/**
	 * 下一个ID（字符串形式）
	 *
	 * @return ID 字符串形式
	 */
	public String nextIdStr() {
		return Long.toString(nextId());
	}

	// ------------------------------------------------------------------------------------------------------------------------------------ Private method start
	/**
	 * 保证返回的毫秒数在参数之后(阻塞到下一个毫秒，直到获得新的时间戳)
	 * 
	 * @param lastTimestamp 上次生成ID的时间截
	 * @return 当前时间戳
	 */
	private long tilNextMillis(long lastTimestamp) {
		long timestamp = genTime();
		while (timestamp <= lastTimestamp) {
		    // 如果发现时间回拨，则自动重新获取（可能会处于无限循环中）
			timestamp = genTime();
		}
		return timestamp;
	}

	/**
	 * 生成时间戳
	 * 
	 * @return 时间戳
	 */
	private long genTime() {
		return this.useSystemClock ? SystemClock.now() : System.currentTimeMillis();
	}
	// ------------------------------------------------------------------------------------------------------------------------------------ Private method end
}